from django.contrib.auth import get_user_model, authenticate
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from axes.backends import AxesBackend
from axes.exceptions import AxesBackendPermissionDenied
from axes.helpers import toggleable
from rest_framework import authentication
from rest_framework.exceptions import AuthenticationFailed

from admin_user.models import AdminUser
from admin_user.serializers import CaptchaSerializer


class HTTP401SessionAuthentication(authentication.SessionAuthentication):
    """
    This class is needed, because REST Framework's default SessionAuthentication does never return 401's,
    because they cannot fill the WWW-Authenticate header with a valid value in the 401 response. As a
    result, we cannot distinguish calls that are not unauthorized (401 unauthorized) and calls for which
    the user does not have permission (403 forbidden). See https://github.com/encode/django-rest-framework/issues/5968

    We do set authenticate_header function in SessionAuthentication, so that a value for the WWW-Authenticate
    header can be retrieved and the response code is automatically set to 401 in case of unauthenticated requests.
    """
    def authenticate_header(self, request):
        return 'Session'


class AxesCaptchaBackend(AxesBackend):
    @toggleable
    def authenticate(self, request, username=None, password=None, **kwargs):
        """如果用户的验证码填写正确，则允许本次请求"""
        try:
            super().authenticate(request, username, password, **kwargs)
        except AxesBackendPermissionDenied:
            # 检查用户是否提供了验证码
            captcha = request.data.get('captcha', {})
            serializer = CaptchaSerializer(data=captcha, context={'request': request})
            if serializer.is_valid():
                return
            # 在request中设置一个标记，供AxesCaptchaAuthentication检查
            request.captcha_error = str(serializer.errors['response'][0])
            raise


class AxesCaptchaAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        raise NotImplementedError()

    def authenticate_credentials(self, userid, password, request):
        """
        如果authenticate失败，检查是否是因为验证码错误导致的
        """
        credentials = {
            get_user_model().USERNAME_FIELD: userid,
            'password': password
        }
        user = authenticate(request=request, **credentials)
        # 管理员账号不属于安全管理员也将返回错误用户名和密码
        if user is None or user.admin_level != AdminUser.STRATEGY_ADMIN:
            raise AuthenticationFailed(getattr(request, 'captcha_error', _('Invalid username/password.')))

        is_active = user.is_active
        if user.admin_level == AdminUser.STRATEGY_ADMIN:
            # 检查安全管理员关联账户是否被禁用或冻结
            related_user = user.related_user
            if related_user.enabled:
                if related_user.blocked:
                    user.is_active = related_user.block_until and related_user.block_until < timezone.now()
                else:
                    user.is_active = True
            else:
                user.is_active = False
        if is_active != user.is_active:
            user.save()

        if not user.is_active:
            raise AuthenticationFailed('用户已锁定或禁用。')

        return user, None
